#!/usr/bin/env python
# coding=utf-8
import ConfigParser
import copy

from common.log import debug, info, warn, error
from common.utils import Singleton

class WorkerConfigParser(ConfigParser.ConfigParser):

    def optionxform(self, optionstr):
        return optionstr

# 通知者
class ConfigureDiscovery(Singleton):
    """
    This class is used for configure discovery,
    support from config file and etcd or rabbitmq.
    """
    config_p = WorkerConfigParser()
    config = {}
    notifiers = {}

    def regist_observer(self, section, obvser_func):
        self.notifiers[section] = obvser_func

    def notify(self):
        # pass the config to each component.
        for k, v in self.config.items():
            if k in self.notifiers:
                self.notifiers[k](**v)
            else:
                warn("Failed to find observer for section %s." % (k))

    def start_observe_conf(self, conf_file):
        self.config_p.read(conf_file)
        sec_list = self.config_p.sections()
        for sec in sec_list:
            sec_dict = None
            if "." in sec:
                sub_secs = sec.split(".")
                sec_dict = self.config
                for sub_sec in sub_secs:
                    if sub_sec not in sec_dict:
                        sec_dict[sub_sec] = {}
                    sec_dict = sec_dict[sub_sec]
            else:
                self.config[sec] = {}
                sec_dict = self.config[sec]
            kv_list = self.config_p.items(sec)
            for kv in kv_list:
                sec_dict[kv[0]] = kv[1]

    def start_observe_etcd(self):
        pass

    def start_observe_rabbitmq(self):
        pass
# 解析规则：
# "." 表示一个字典的级联关系（比如 input 下的一个映射 a.b 会被翻译为 input_data["a"]["b"]）
# "|" 表示等号左边的值可以为右边被 "|" 分割开的任意值（比如 a=b|c 会被翻译 a=b 或者 a=c，如果b c同时存在
# 会被重复赋值结果可能是b可能是c所以一般用在b c不同时存在的情况）
# "&" 表示这个值的存在有多个key，然后是取最后一个key对应值作为这个最终值，用来表示多key的情况下值的区分
# "."下面可以有 "|" "&"
class Cmop2ParametersAdapter(Singleton):
    input_mapping_dict = None
    output_mapping_dict = None

    def input_notify(self, **kwargs):
        self.input_mapping_dict = copy.deepcopy(kwargs)

    def output_notify(self, **kwargs):
        self.output_mapping_dict = copy.deepcopy(kwargs)

    def _k_v_io(self, input_data, output_data, i_k, o_k):
        if "." in o_k:
            sub_o_ks = o_k.split(".")
            sub_output_dict = output_data
            for sub_o_k in sub_o_ks[:-1]:
                if sub_o_k not in sub_output_dict:
                    sub_output_dict[sub_o_k] = {}
                sub_output_dict = sub_output_dict[sub_o_k]
            if "|" not in i_k:
                if i_k in input_data:
                    sub_output_dict[sub_o_ks[-1]] = input_data[i_k]
                else:
                    debug("input key %s is not in input data %s." % (i_k, input_data))
            else:
                i_k_list = i_k.split("|")
                for n_i_k in i_k_list:
                    if n_i_k in input_data:
                        sub_output_dict[sub_o_ks[-1]] = input_data[n_i_k]
                    elif "&" in n_i_k:
                        n_i_k_list = n_i_k.split("&")
                        last_key = n_i_k_list[-1]
                        del n_i_k_list[-1]
                        for s_n_i_k in  n_i_k_list:
                            if s_n_i_k not in input_data:
                                break
                        else:
                            if last_key in input_data:
                                sub_output_dict[sub_o_ks[-1]] = input_data[last_key]
        else:
            if "|" not in i_k:
                output_data[o_k] = input_data[i_k]
            else:
                i_k_list = i_k.split("|")
                for n_i_k in i_k_list:
                    if n_i_k in input_data:
                        output_data[o_k] = input_data[n_i_k]
                    elif "&" in n_i_k:
                        n_i_k_list = n_i_k.split("&")
                        last_key = n_i_k_list[-1]
                        del n_i_k_list[-1]
                        for s_n_i_k in n_i_k_list:
                            if s_n_i_k not in input_data:
                                break
                        else:
                            if last_key in input_data:
                                output_data[o_k] = input_data[last_key]

        return output_data

    def _multi_k_v_io(self, input_data, output_data, i_k, o_k):
        res = None
        k_list = i_k.split('.')
        sub_i_k = k_list[-1]
        sub_input_data = input_data
        del k_list[-1]
        for k in k_list:
            if k in sub_input_data:
                sub_input_data = sub_input_data[k]
            else:
                break

        if sub_input_data != input_data and sub_input_data:
            res = self._k_v_io(sub_input_data, output_data, sub_i_k, o_k)
        else:
            warn("Failed to get sub input data, ignore ...")

        return res

    def input_adapt(self, input_data):
        """
        Make input data fit with own data
        :param self:
        :param input_data:
        :return: output_data
        """
        output_data = {}

        if "op" in self.input_mapping_dict:
            for o_k, i_k in self.input_mapping_dict["op"].items():
                if i_k in input_data or "|" in i_k:
                    self._k_v_io(input_data, output_data, i_k, o_k)
                elif '.' in i_k:
                    self._multi_k_v_io(input_data, output_data, i_k, o_k)
                # else:
                #     debug("Optional mapping key %s is not in input data." % (i_k))

        for o_k, i_k in self.input_mapping_dict["req"].items():
            if i_k in input_data or "|" in i_k:
                self._k_v_io(input_data, output_data, i_k, o_k)
            elif '.' in i_k:
                res = self._multi_k_v_io(input_data, output_data, i_k, o_k)
                if res is None:
                    error("Required mapping keys %s is not in input data." % (i_k))
            else:
                error("Required mapping key %s is not in input data." % (i_k))

        for k, v in input_data.items():
            if v is None:
                continue
            nk = "%s%s" % (k[0].upper(), k[1:])
            if nk not in output_data:
                output_data[nk] = v
        # debug("output data %s" % (output_data))
        return output_data

    def output_adapt(self, input_data):
        """
        Make output data fit with user needed.
        :param self:
        :param input_data:
        :return: output_data
        """
        output_data = {}

        if "op" in self.output_mapping_dict:
            for o_k, i_k in self.output_mapping_dict["op"].items():
                if i_k in input_data or "|" in i_k:
                    self._k_v_io(input_data, output_data, i_k, o_k)
                elif '.' in i_k:
                    self._multi_k_v_io(input_data, output_data, i_k, o_k)

        for o_k, i_k in self.output_mapping_dict["req"].items():
            if i_k in input_data or "|" in i_k:
                self._k_v_io(input_data, output_data, i_k, o_k)
            elif '.' in i_k:
                res = self._multi_k_v_io(input_data, output_data, i_k, o_k)
                if res is None:
                    error("Required mapping keys %s is not in output data." % (i_k))
            else:
                error("Required mapping key %s is not in output data." % (i_k))

        # value convert
        if "valconv" in self.output_mapping_dict:
            for k, v in self.output_mapping_dict["valconv"].items():
                if k in output_data:
                    if output_data[k] in v:
                        output_data[k] = v[output_data[k]]
                    elif "others" in v:
                        output_data[k] = v["others"]

        return output_data
